我們接下來會建立以下幾個頁面
接下來是任務頁面!
首先我們來把查看任務的頁面做出來
task
@login_required
:這個裝飾器確保只有已登入的使用者才能訪問該頁面。user = request.user
:取得當前登入的使用者。tasks = Task.objects.filter(user_id=user)
:查詢該使用者的所有任務。sharedTasks = SharedTask.objects.filter(Q(user=user) | Q(task__user_id=user))
:使用Q物件查詢與當前使用者相關的共享任務(無論是共享給他或是他創建的任務)。my_tasks = tasks.exclude(id__in=sharedTasks.values_list('task__id', flat=True))
:過濾掉共享任務,僅保留當前使用者的私有任務。
迴圈內為每個私有任務取得相關留言。
views.py
@login_required
def task(request):
user = request.user
tasks = Task.objects.filter(user_id=user)
# 取得共享的任務
sharedTasks = SharedTask.objects.filter(Q(user=user) | Q(task__user_id=user))
# 排除共享任務後的我的任務
my_tasks = tasks.exclude(id__in=sharedTasks.values_list('task__id', flat=True))
# 為每個任務獲取留言
for task in my_tasks:
task.comments = Comment.objects.filter(task=task)
return render(request, 'task.html',locals())
base.html
是其他繼承base.html
的基礎,使用簡單的JavaScript函式toggleDetails來控制任務詳情的顯示和隱藏,透過按鈕點擊事件來切換詳細內容的顯示狀態。
{% load static %}
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TASK</title>
<link rel="stylesheet" href="{% static 'css/task.css' %}">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css"> <!-- Font Awesome -->
<script>
// 切換顯示任務詳情
function toggleDetails(id) {
var details = document.getElementById('details-' + id);
if (details.style.display === 'none' || details.style.display === '') {
details.style.display = 'block';
} else {
details.style.display = 'none';
}
}
</script>
</head>
<body>
<div class="container">
<!-- 左側欄 -->
<nav class="sidebar">
<h2>我的任務</h2>
<ul>
<li><a href="/task/">我的任務</a></li>
</ul>
<h2>共享的任務</h2>
<ul>
{% if sharedTasks %}
<li><a href="/task/show_share/">共享任務</a></li>
{% else %}
<li>沒有共享任務</li>
{% endif %}
</ul>
<h2>{{user.username}}</h2>
<ul>
<form method="post" action="{% url 'logout' %}">
{% csrf_token %}
<button type="submit" class="logout-link">登出</button>
</form></li>
</ul>
</nav>
<!-- 右側任務列表 -->
<section class="main-content">
<h1>任務列表<a href="{% url 'add_task' %}" class="icon-link"><i class="fas fa-plus"></i> 新增</a></h1>
{% block content %}
{% endblock %}
</section>
</div>
</body>
</html>
task.html
使用了Bootstrap和Font Awesome來美化頁面和顯示圖標。
左側欄顯示「我的任務」和「共享的任務」分區,並根據是否有共享任務動態顯示「共享任務」連結。
右側是任務列表,按鈕可以顯示每個任務的詳情,並提供了編輯、刪除、共享和留言的操作按鈕。
任務的詳情以可展開/折疊的形式展示,並列出該任務的留言。
通過{% extends 'base.html' %},task.html
繼承了基本的HTML框架,只修改了content部分來顯示任務列表。
{% extends 'base.html' %}
{% block content %}
<div>
{% for task in my_tasks %}
<div class="task">
<h3></h3>
<div class="task_line">
<button class="task-button" onclick="toggleDetails({{ task.id }})">{{ task.title }}</button>
<a class="icon-link" href="/task/edit/{{task.id}}"><i class="fa fa-edit"></i> 編輯</a>
<a class="icon-link" href="/task/delete/{{task.id}}" onclick="return confirm('確定要刪除這個任務嗎?');"><i class="fa fa-trash"></i> 刪除</a>
<a class="icon-link" href="/task/share/{{task.id}}"><i class="fa fa-share"></i> 共享</a>
<a class="icon-link" href="/task/comment/{{task.id}}"><i class="fa fa-comment"></i> 留言</a>
<a class="icon-link" href="/task/show_log/{{task.id}}"><i class="fas fa-history"></i> 變更歷史</a>
</div>
<div id="details-{{ task.id }}" class="task-details">
<p>任務: {{task.title}}</p>
<p>描述: {{ task.description }}</p>
<p>截止日期: {{ task.due_date }}</p>
<p>優先權: {{ task.priority }}</p>
<p>狀態: {{ task.status }}</p>
<hr>
<h4>留言:</h4>
{% for comment in task.comments %}
<div class="comment">
<p><strong>{{ comment.user.username }}:</strong> {{ comment.content }} <em>({{ comment.created_at|date:"Y-m-d H:i" }})</em></p>
</div>
{% empty %}
<p>沒有留言。</p>
{% endfor %}
</div>
</div>
{% empty %}
<p>沒有任務</p>
{% endfor %}
</div>
{% endblock %}
定義了網站的主要配色,包括深綠色、淺綠色、白色等作為背景和字體顏色,並使用了線性漸變來設置背景。
使用了不同的樣式來控制左側欄位(sidebar)和右側主內容區(main-content)的佈局和外觀,確保頁面整潔且易於閱讀。
調整了按鈕、鏈接和留言框的樣式,提供了符合現代網頁設計風格的互動效果(如懸停時變色)。
body {
height: auto;
text-align: center;
font-family: Arial, sans-serif;
}
body::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: url('../images/logo.png');
background-repeat: repeat;
background-size: 5%;
opacity: 0.5;
z-index: -1;
}
body {
background: linear-gradient(to right, rgba(240, 244, 195, 1), rgba(165, 214, 167, 1)); /* 背景漸變 */
}
/* 背景與主色 */
:root {
--dark-green: #1c3720;
--light-green: #a5d6a7;
--green : #70aa72;
--dark-gray: #333333;
--white: #ffffff;
--gray: #e0e0e0;
}
h1{
color: var( --dark-green);
}
h2 {
color: var(--light-green);
}
a {
color: var(--white);
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
.container {
display: flex;
height: 97vh;
border-radius: 20px;
}
/* 左側欄位 */
.sidebar {
width: 20%;
background-color: var(--dark-green);
padding: 20px;
box-sizing: border-box;
border-radius: 20px;
}
.sidebar h2 {
margin-top: 0;
}
.sidebar ul {
list-style-type: none;
padding-left: 0;
}
.sidebar ul li {
margin: 10px 0;
}
.sidebar ul li a {
padding: 10px;
display: block;
background-color: var(--green);
border-radius: 5px;
}
.sidebar ul li a:hover {
background-color: var(--light-green);
color: var(--dark-gray);
}
/* 右側內容區 */
.main-content {
width: 80%;
background-color: var(--gray);
padding: 40px;
box-sizing: border-box;
overflow-y: auto;
border-radius: 20px;
}
.main-content h1 {
border-bottom: 2px solid var(--dark-green);
padding-bottom: 10px;
}
.main-content p {
margin: 20px 0;
color: var(--dark-gray);
}
.task_line{
width: 100%;
}
.task-button {
padding: 10px 15px;
border: none;
border-radius: 5px;
background-color: var(--green);
color: var(--white);
cursor: pointer;
transition: background-color 0.3s;
font-size: 16px; /* 增加字體大小 */
}
.task-button:hover {
background-color: var(--light-green);
}
/* 圖標鏈接樣式 */
.icon-link {
display: inline-flex;
align-items: center; /* 垂直置中 */
margin-left: 10px; /* 添加間距 */
color: var(--dark-gray);
text-decoration: none;
transition: color 0.3s;
font-size: 14px; /* 字體大小 */
}
.icon-link:hover {
color: var(--green);
}
/* 圖標樣式 */
.icon-link i {
margin-right: 5px; /* 圖標和文本之間的間距 */
}
/* 任務詳情的樣式 */
.task-details {
display: none;
margin-top: 10px;
padding: 15px; /* 增加內邊距 */
background-color: #f9f9f9;
border: 1px solid #ddd;
border-radius: 5px; /* 圓角 */
text-align: left;
}
.add_task {
background-color: #f9f9f9; /* 背景顏色 */
border: 1px solid #ccc; /* 邊框 */
border-radius: 8px; /* 圓角 */
padding: 20px; /* 內邊距 */
max-width: 500px; /* 最大寬度 */
margin: 20px auto; /* 上下邊距*/
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); /*陰影 */
text-align: left;
}
.add_task h1 {
font-size: 24px;
color: #333;
margin-bottom: 15px;
}
.add_task form {
display: flex;
flex-direction: column;
}
.add_task button.task-button {
background-color: #28a745;
color: white;
border: none;
padding: 10px 15px;
border-radius: 5px;
cursor: pointer;
transition: background-color 0.3s;
}
.add_task button.task-button:hover {
background-color: #218838;
}
.add_task a {
display: inline-block;
margin-top: 15px;
text-decoration: none;
color: #007bff;
}
.add_task a:hover {
text-decoration: underline;
}
.logout-link {
color: var(--white);
background-color: #66bd7a;
text-decoration: none;
padding: 10px;
border-radius: 5px;
width: 100%;
}
.logout-link:hover {
background-color: var(--light-green);
color: var(--dark-gray);
}
任務未展開的畫面
任務展開的畫面
接下來,實作創建任務的表單
add_task
在 add_task
函式中,使用了 Django 的 @login_required
裝飾器,確保只有已登入的用戶才能訪問這個頁面。這樣的設計能增強系統的安全性。
當用戶提交表單時,程式會檢查 POST 請求,如果表單有效,便會新增任務並保存至資料庫,並顯示一條成功訊息。
views.py
@login_required
def add_task(request):
user = request.user
tasks = Task.objects.filter(user_id=user)
# 取得共享的任務
sharedTasks = SharedTask.objects.filter(Q(user=user) | Q(task__user_id=user))
# 排除共享任務後的我的任務
my_tasks = tasks.exclude(id__in=sharedTasks.values_list('task__id', flat=True))
if request.method == 'POST':
form = TaskForm(request.POST)
if form.is_valid():
try:
id = Task.objects.latest('id').id + 1
except:
id = 0
title = form.cleaned_data['title']
description = form.cleaned_data['description']
due_date = form.cleaned_data['due_date']
priority = form.cleaned_data['priority']
status = form.cleaned_data['status']
user_id = request.user
task = Task(
id = id,
title = title,
description = description,
due_date = due_date,
priority = priority,
status = status,
user_id = user_id
)
task.save() # 保存
messages.success(request, '任務已新增!')
return redirect('task') # 重定向到任務列表頁面
else:
form = TaskForm()
return render(request, 'add_task.html', locals())
URL
add_task
函式與路徑 task/add/
綁定,讓使用者能通過此路徑訪問新增任務的頁面。
path('task/add/', add_task, name='add_task'), # 新增任務的路由
add_task.html
在這個模板中,透過 {{ form.as_p }}
渲染表單,這樣能讓表單自動以 p
標籤包裝每一個欄位。CSRF
保護標籤 csrf_token
則是避免 CSRF
攻擊。此外,還提供了一個返回任務列表的連結。
{% extends 'base.html' %}
{% block content %}
<div class="add_task">
<h1>新增任務</h1>
<form method="post">
{% csrf_token %}
{{ form.as_p }} <!-- 渲染表單 -->
<button type="submit" class="task-button">新增任務</button>
</form>
<a href="{% url 'task' %}">返回任務列表</a>
</div>
{% endblock%}
TaskForm
TaskForm
使用 Django
的 forms.ModelForm
,將表單與 Task
模型關聯,並且指定了要使用的欄位。每個欄位都有對應的屬性(如最大長度、輸入格式等)。
forms.py
class TaskForm(forms.ModelForm):
class Meta:
model = Task
fields = ['title', 'description', 'due_date', 'priority', 'status'] # 指定要使用的欄位
title = forms.CharField(label='任務名稱', max_length=100)
description = forms.CharField(label='描述', widget=forms.Textarea(attrs={'name': 'body'}))
due_date = forms.DateTimeField(label='到期日', widget=forms.DateTimeInput(attrs={'type': 'datetime-local'}),input_formats=['%Y-%m-%dT%H:%M'])
priority = forms.IntegerField(label='優先權')
status_choice = [
['待辦', '待辦'],
['進行中', '進行中'],
['完成', '完成']
]
status = forms.ChoiceField(label='狀態', choices=status_choice)
新增表單
新增後表單出現在任務列表中